跳到主要内容

快速上手

基本概念

quick start: https://guides.rubyonrails.org/getting_started.html

  • Route(路由):rules written in a Ruby DSL (Domain-Specific Language).

  • Controller(控制器):Ruby classes, and their public methods are actions(动作)

  • Views(视图):templates, usually written in a mixture of HTML and Ruby

    • 数据是在控制器中获取的,而不是在视图中。视图只是把数据显示出来

创建新项目

Installing Rails

gem install rails

新建一个应用(名为 blog)

rails new blog

运行项目

bin/rails server

项目结构

File/FolderPurpose
app/Contains the controllers, models, views, helpers, mailers, channels, jobs, and assets for your application. You'll focus on this folder for the remainder of this guide.
bin/Contains the rails script that starts your app and can contain other scripts you use to set up, update, deploy, or run your application.
config/Contains configuration for your application's routes, database, and more. This is covered in more detail in Configuring Rails Applications.
config.ruRack configuration for Rack-based servers used to start the application. For more information about Rack, see the Rack website.
db/Contains your current database schema, as well as the database migrations.
DockerfileConfiguration file for Docker.
Gemfile Gemfile.lockThese files allow you to specify what gem dependencies are needed for your Rails application. These files are used by the Bundler gem. For more information about Bundler, see the Bundler website.
lib/Extended modules for your application.
log/Application log files.
public/Contains static files and compiled assets. When your app is running, this directory will be exposed as-is.
RakefileThis file locates and loads tasks that can be run from the command line. The task definitions are defined throughout the components of Rails. Rather than changing Rakefile, you should add your own tasks by adding files to the lib/tasks directory of your application.
README.mdThis is a brief instruction manual for your application. You should edit this file to tell others what your application does, how to set it up, and so on.
storage/Active Storage files for Disk Service. This is covered in Active Storage Overview.
test/Unit tests, fixtures, and other test apparatus. These are covered in Testing Rails Applications.
tmp/Temporary files (like cache and pid files).
vendor/A place for all third-party code. In a typical Rails application this includes vendored gems.
.dockerignoreThis file tells Docker which files it should not copy into the container.
.gitattributesThis file defines metadata for specific paths in a git repository. This metadata can be used by git and other tools to enhance their behavior. See the gitattributes documentation for more information.
.github/Contains GitHub specific files.
.gitignoreThis file tells git which files (or patterns) it should ignore. See GitHub - Ignoring files for more information about ignoring files.
.rubocop.ymlThis file contains the configuration for RuboCop.
.ruby-versionThis file contains the default Ruby version.

Route

Let's start by adding a route to our routes file, config/routes.rb, at the top of the Rails.application.routes.draw block:

# config/routes.rb
Rails.application.routes.draw do
get "/articles", to: "articles#index"

# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

The route above declares that GET /articles 请求会被匹配到 ArticlesController 控制器的 index 动作上

运行 controller generator (with the --skip-routes option 因为已经有了 route 定义),来创建 ArticlesController

bin/rails generate controller Articles index --skip-routes

Rails will create several files for you:

create  app/controllers/articles_controller.rb
invoke erb
create app/views/articles
create app/views/articles/index.html.erb
invoke test_unit
create test/controllers/articles_controller_test.rb
invoke helper
create app/helpers/articles_helper.rb
invoke test_unit

其中 controller file 是最重要的, app/controllers/articles_controller.rb. Let's take a look at it:

class ArticlesController < ApplicationController
def index
end
end

index action 暂时没有内容. 当一个 action 没有明确地渲染一个 view (或 trigger an HTTP response) 时,Rails 会自动根据控制器和动作的名字而渲染一个默认的 view. 约定优于配置!(Convention Over Configuration)

Views 集中在 app/views 目录中. 故 index action 会默认渲染 app/views/articles/index.html.erb

打开 app/views/articles/index.html.erb,将内容替换为:

<h1>Hello, Rails!</h1>

运行服务:

bin/rails server

设置主页 route

使用 root 参数

Rails.application.routes.draw do
root "articles#index"

# ...
end

Autoloading

Rails 应用不需要使用 require 来导入应用内的代码

比如:ArticlesController inherits from ApplicationController, 但 app/controllers/articles_controller.rb does not have anything like

require "application_controller" # DON'T DO THIS.

你不需要也不应该在 app 目录内使用 require 导入任何东西. 这个特性叫做 autoloading

you can learn more about it in Autoloading and Reloading Constants.

在两种情况下,会用到 require

  • To load files under the lib directory
  • To load gem dependencies that have require: false in the Gemfile

MVC

routes, controllers, actions, and views…… 所有这些组件都是 MVC 思想的一部分。MVC is a design pattern that divides the responsibilities of an application to make it easier to reason about. Rails follows this design pattern by convention.

Model

Model 用于描述数据,它可以与应用数据库进行互动(这个特性被称作 Active Record)

To define a model, we will use the model generator:

bin/rails generate model Article title:string body:text

Model names are singular, because an instantiated model represents a single data record

This will create several files:

invoke  active_record
create db/migrate/<timestamp>_create_articles.rb // migration file
create app/models/article.rb // model file
invoke test_unit
create test/models/article_test.rb
create test/fixtures/articles.yml

数据库迁移 Database Migrations

Let's take a look at the contents of our new migration file:

class CreateArticles < ActiveRecord::Migration[7.2]
def change
create_table :articles do |t|
t.string :title
t.text :body

t.timestamps
end
end
end

The call to create_table specifies how the articles table should be constructed. create_table method 默认添加了 id 字段作为一个自增的主键。

create_table block 中, two columns are defined: title and body;这俩字段的定义是自动生成的,因为我们的命令包含了它们

最后一行调用了 t.timestamps,这个方法定义了两个默认的字段:created_at and updated_at. As we will see, Rails will manage these for us, setting the values when we create or update a model object.

Let's run our migration with the following command:

bin/rails db:migrate

The command will display output indicating that the table was created:

==  CreateArticles: migrating ===================================
-- create_table(:articles)
-> 0.0018s
== CreateArticles: migrated (0.0018s) ==========================

Model 与 database 的互动

console 是 Rails 自带的一个交互式编程环境,类似于 irb

Let's launch the console with this command:

bin/rails console

You should see an irb prompt like:

Loading development environment (Rails 7.2.0)
irb(main):001:0>

实例化一个 Article object:

irb> article = Article.new(title: "Hello Rails", body: "I am on Rails!")

到此为止,我们只是实例化(initialized)了一个对象,并未写入数据库。

To save the object to the database, we must call save:

irb> article.save
(0.1ms) begin transaction
Article Create (0.4ms) INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]]
(0.9ms) commit transaction
=> true

此时,这个实例正式被写入数据库。查看之:

irb(main):003> article
=>
#<Article:0x000056369360dba8
id: 1,
title: "Hello Rails",
body: "I am on Rails!",
created_at: Mon, 12 Aug 2024 09:39:15.610309000 UTC +00:00,
updated_at: Mon, 12 Aug 2024 09:39:15.610309000 UTC +00:00>

使用 find 方法,传入 id 来查询

irb(main):004> Article.find(1)
Article Load (0.3ms) SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
=>
#<Article:0x0000563693243068
id: 1,
title: "Hello Rails",
body: "I am on Rails!",
created_at: Mon, 12 Aug 2024 09:39:15.610309000 UTC +00:00,
updated_at: Mon, 12 Aug 2024 09:39:15.610309000 UTC +00:00>

使用 all 来获取所有记录

irb(main):005> Article.all
Article Load (0.2ms) SELECT "articles".* FROM "articles" /* loading for pp */ LIMIT ? [["LIMIT", 11]]
=>
[#<Article:0x000056369338c500
id: 1,
title: "Hello Rails",
body: "I am on Rails!",
created_at: Mon, 12 Aug 2024 09:39:15.610309000 UTC +00:00,
updated_at: Mon, 12 Aug 2024 09:39:15.610309000 UTC +00:00>]

这个方法返回 ActiveRecord::Relation 对象,可以理解为一个 super-powered array.

Models are the final piece of the MVC puzzle. Next, we will connect all of the pieces together.

Showing a List of Articles

app/controllers/articles_controller.rb 中,改造 controller 的 index action,以期拉取数据库中所有 Article

class ArticlesController < ApplicationController
def index
@articles = Article.all
end
end

Controller 实例的变量能被 view 访问. That means we can reference @articles in app/views/articles/index.html.erb. Let's open that file, and replace its contents with:

<h1>Articles</h1>

<ul>
<% @articles.each do |article| %>
<li>
<%= article.title %>
</li>
<% end %>
</ul>

上面的代码混合了 HTML and ERB(ERB, short for Embedded Ruby, 是一个将 Ruby 嵌入到 Document 中的模板系统)

可以看到两种 ERB tags: <% %> and <%= %>

  • The <% %> tag means "evaluate the enclosed Ruby code."
  • The <%= %> tag means "evaluate the enclosed Ruby code, and output the value it returns."

在常规 Ruby 中的所有代码都能正常运行在 ERB 标签中,不过尽量保证语法的简洁

We can see the final result by visiting http://localhost:3000. (Remember that bin/rails server must be running!) Here's what happens when we do that:

  1. The browser makes a request: GET http://localhost:3000.
  2. Our Rails application receives this request.
  3. The Rails router maps the root route to the index action of ArticlesController.
  4. The index action uses the Article model to fetch all articles in the database.
  5. Rails automatically renders the app/views/articles/index.html.erb view.
  6. The ERB code in the view is evaluated to output HTML.
  7. The server sends a response containing the HTML back to the browser.

We've connected all the MVC pieces together, and we have our first controller action! Next, we'll move on to the second action.

CRUDit Where CRUDit Is Due

Rails provides many features to help simplify code doing CRUD.

Showing a Single Article

Let's add a new view that shows the title and body of a single article.

先创建一个新的 route,去匹配新的 controller action (which we will add next)

Open config/routes.rb, and insert the last route shown here:

Rails.application.routes.draw do
root "articles#index"

get "/articles", to: "articles#index"
get "/articles/:id", to: "articles#show" # new route
end

below the index action in app/controllers/articles_controller.rb,添加 show 方法,获取指定 id 的 article

class ArticlesController < ApplicationController
def index
@articles = Article.all
end

def show
@article = Article.find(params[:id])
end
end
  • show 方法 的 id 参数由 /articles/:id 传递而来,被称为 route parameter

  • show 的返回值被存在 @article 中, so it is accessible by the view. By default, the show action will render app/views/articles/show.html.erb.

Let's create app/views/articles/show.html.erb, with the following contents:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

Now we can see the article when we visit http://localhost:3000/articles/1!

顺便优化下 app/views/articles/index.html.erb,创建链接标签定向至单个 article

<h1>Articles</h1>

<ul>
<% @articles.each do |article| %>
<li>
<a href="/articles/<%= article.id %>">
<%= article.title %>
</a>
</li>
<% end %>
</ul>